package testdatagenerator;

import java.util.Random;
import java.util.Vector;


public class Node {
	private static Node prev;
	private static Vector<Node> allNodes = null;
	private static Random gen = new Random();
	private int packets;
	private Vector<Node> neighbors;
	private boolean attackSet;
	private double meanDelay, stdDevDelay, DropRate;
	private int id, currDestDist, ip, msgseqNum, packseqNum;
	private packet heldData;
	
	//Return the ip of a node with its id = id
	public static int getIpById(int id){
		return id+16843008;
	}
	
	//Retrieves a node with id number id
	public static Node getNodeById(int id){
		int i;
		for(i=0;i<allNodes.size();i++){
			if (allNodes.get(i).id==id){
				break;
			}
		}
		if (i==allNodes.size()){
			return null;
		} else {
			return allNodes.get(i);
		}
	}
	
	public static Node getHighlyConnectedNode(){
		int maxCon = 0;
		Node mostConnected = null;
		for(int i=0;i<allNodes.size();i++){
			Node temp = allNodes.get(i);
			if (temp.getNumNeigh()> maxCon){
				maxCon = temp.getNumNeigh();
				mostConnected = temp;
			}
		}
		return mostConnected;
	}
	
	public static String printRelevantNodeData(){
		String output="";
                for(int i=0;i<allNodes.size();i++){
			Node s = allNodes.get(i);
			output = output + "Node "+s.id+" sent "+s.packets+" packets with a mean delay of "+s.meanDelay+" and it had "+s.getNumNeigh()+" neighbors." + "\n";
		}
                return output;
	}
	
	public static int getNumNodes(){
		if (allNodes!=null){
			return allNodes.size();
		} else {
			return 0;
		}
	}
	
	public static void deleteAllNodes(){
		if (allNodes!=null){
			int size = allNodes.size();
			for(int i=0;i<size;i++){
				allNodes.remove(0);
			}
		}	
	}
	
	public Node(int id, double meanDelay, double stdDev, double DropRate){
		attackSet = false;
		msgseqNum = 0;
		packseqNum = 0;
		packets = 0;
		ip = id+16843008;
		neighbors = new Vector<Node>();
		if (allNodes==null){
			allNodes = new Vector<Node>();
		}
		this.id = id;
		this.meanDelay = meanDelay; 
		this.stdDevDelay = stdDev;
		this.DropRate = DropRate;
		allNodes.add(this);
		heldData = null;
	}
	
	//set the DropRate to d if it is worse then the current one
	public void setDropRateIfGreater(double d){
		if (d > DropRate || !attackSet){
			DropRate = d;
		}
	}
	
	public void setAttack(){
		attackSet = true;
	}
	
	//Make this node have a packet to send
	public void givePacket(packet p){
		packets++;
		heldData = p;
		if (gen.nextDouble() < .7){
			packseqNum++;
		} else {
			msgseqNum++;
			packseqNum = 0;
		}
		p.setMsgSeqNum(msgseqNum);
		p.setPackSeqNum(packseqNum);
		p.setOrigin(id);
	}
	
	//Get the id of a neighbor
	public int getNeighId(){
		return neighbors.get(0).id;
	}
	
	
	//return whether or not all of the nodes in nodes are a neighbor to this node
	public boolean isNeighbor(Vector<Node> nodes){
		for (int i=0;i<nodes.size();i++){
			if (!isNeighbor(nodes.get(i).getId())){
				return false;
			}
		}
		return true;
	}
	
	//return whether or not the node with the given id is a neighbor to this node
	public boolean isNeighbor(int id){
		if (id==-1){
			return true;
		}
		for(int i=0;i<neighbors.size();i++){
			if (neighbors.get(i).id==id){
				return true;
			}
		}
		return false;
	}
	
	//get number of neighbors of said Node
	public int getNumNeigh(){
		return neighbors.size();
	}
	
	//alert the set of all nodes that this node no longer exists
	public void close(){
		allNodes.remove(this);
	}
	
	//get the ip of the current node
	public int getIp(){
		return ip;
	}
	
	//set the ip of the current node
	public void setIp(int IP){
		ip = IP;
	}
	
	//set the meanDelay to mean if it is worse then the current one
	public void setMeanIfGreater(double mean){
		if (mean > meanDelay || !attackSet){
			meanDelay = mean;
		}
	}
	
	//set the standard deviation to stdDev if it is worse then the current one
	public void setDevIfGreater(double stdDev){
		if (stdDev > stdDevDelay || !attackSet){
			stdDevDelay = stdDev;
		}
	}
	
	//establish the is a neighbor relationship between these nodes
	public void makeNeighbors(Node n){
		neighbors.add(n);
		n.neighbors.add(this);
	}
	
	//get this nodes id
	public int getId(){
		return id;
	}
	
	//pass the data held by this node on to another node
	private void passData(GeneralData theData, Node n){
		char opmode = 'r';
		packet thePacket = heldData;
		if (thePacket.getOrigin()==id){
			prev = null;
			opmode = 's';
		}
		if (opmode != 's' && gen.nextDouble() < DropRate){
			System.out.println("Packet Dropped");
			heldData = null;
			return;
		}
		theData.decTrans();
		if (prev != null){
			thePacket.updatePacket(prev.getDelay(), prev, this, n, opmode);
		} else {
			thePacket.updatePacket(0, prev, this, n, opmode);
		}
		prev = this;
		n.heldData = thePacket;
		heldData = null;
		if (thePacket.getDest()==n.id){
			theData.decTrans();
			thePacket.updatePacket(prev.getDelay(), this, n, null, 'd');
		}
	}
	
	//makeDataPath() must always be called before this method
	//send the data this node holds through all the needed nodes until it arrives at destination
	private void pushDataToDest(GeneralData theData){
		int min = -1;
		if (heldData.getTimeToLive() < 1){
			heldData.logFailure();
			return;
		}
		if (heldData.getVtime().before(heldData.getTime())){
			heldData.logFailure();
			return;
		}
		if (heldData.getDest()==id){
			heldData.logSuccess();
			return;
		}
		Node minNode = null, temp;
		for(int i=0;i<neighbors.size();i++){
			temp = neighbors.elementAt(i);
			if (min==-1 || temp.getDist() < min){
				min = temp.getDist();
				minNode = temp;
			}
		}
		passData(theData, minNode);
		if (minNode.heldData==null){
			return;
		}
		minNode.pushDataToDest(theData);
	}
	
	//pick any node at random
	public static Node getRandom(){
		return allNodes.get(gen.nextInt(allNodes.size()));
	}
	
	//retrieve neighbor by its index in the vector
	//this is mainly for iterating through the list of neighbors
	public Node getNeigh(int whichOne){
		return neighbors.get(whichOne);
	}
	
	//Get a random node that is not a neighbor of this node
	public Node pickRandomNonNeighbor(){
		if (neighbors.size()>=allNodes.size()){
			return null;
		}
		Vector<Node> Nodes = new Vector<Node>();
		for(int i=0;i<allNodes.size();i++){
			if(!isNeighbor(i)){
				Nodes.add(Node.getNodeById(i));
			}	
		}
		return Nodes.get(gen.nextInt(Nodes.size()));
	}
	
	//send this nodes data to through all necessary nodes and ultimately to destination
	public void send(GeneralData theData){
		Node.makeDataPath(heldData);
		pushDataToDest(theData);
	}
	
	//Sets the currDestDist for all nodes so that pushDataToDest()
	// may know what nodes to send the data through
	private static void makeDataPath(packet beingSent){
		Node dest = Node.getNodeById(beingSent.getDest());
		dest.setDist(0);
		Vector<Node> gen1 = new Vector<Node>();
		gen1.add(dest);
		makeDataPath(null, gen1, 1);
	}
	
	//recursive tail-end of previous method
	private static void makeDataPath(Vector<Node> prevGen, Vector<Node> generation, int depth){
		Vector<Node> nextGen = new Vector<Node>();
		if (generation.size()==0){
			return;
		}
		for(int i=0;i<generation.size();i++){
			Node temp = generation.get(i);
			for(int j=0;j<temp.neighbors.size();j++){
				Node temp2 = temp.neighbors.get(j);
				if (!generation.contains(temp2) && (prevGen==null || !prevGen.contains(temp2))){
					temp2.setDist(depth);
					nextGen.add(temp2);
				}	
			}
		}
		makeDataPath(generation, nextGen, depth+1);
	}
	
	//get this node's distance (in nodes) from the current destination
	private int getDist(){
		return currDestDist;
	}
	
	//tell this node how far it is (in nodes) from the current destination
	private void setDist(int dist){
		currDestDist = dist;
	}
	
	//get how long it took this packet to transfer from one node to the next
	private double getDelay(){
		double ret = meanDelay + gen.nextGaussian()*stdDevDelay;
		if (ret < .001){
			ret = .001-gen.nextDouble()/1500;
		}
		return ret;
	}
}
